﻿/*******************************************************************************
 * AAEngine
 * Copyright (c) 2010 Mike Jarosch
 *
 * Original source PushButton Engine:
 * Copyright (C) 2009 PushButton Labs, LLC
 * For more information see http://www.pushbuttonengine.com
 *
 * This file is licensed under the terms of the MIT license, which is included
 * in the "MIT License.txt" file at the root directory of this SDK.
 ******************************************************************************/

using System;
using Microsoft.Xna.Framework;
using Box2D.XNA;
using AAEngine.Engine;
using AAEngine.Engine.Core;
using AAEngine.Engine.Debug;
using AAEngine.Engine.Entities;
using AAEngine.Rendering2D;

namespace AAEngine.Box2D
{
    public class Box2DSpatialComponent : EntityComponent, IMobileSpatialObject2D
    {
        public Action<Box2DSpatialComponent> OnAddedCallback { get; set; }

        private Box2DManagerComponent _manager;
        private ObjectType _collidesWithTypes;
        private ObjectType _collisionType;

        private Vector2 _size = new Vector2(10, 10);
        
        private bool _canMove = true;
        private bool _canRotate = true;

        private Vector2 _linearVelocity = Vector2.Zero;
        private float _angularVelocity = 0;
        private bool _canSleep = true;

        private CollisionShape[] _collisionShapes;

        private Body _body;
        private BodyDef _bodyDef = new BodyDef();

        public Box2DManagerComponent SpatialManager
        {
            get { return _manager; }
            set
            {
                if (_body != null)
                {
                    Logger.Warn(this, "set_SpatialManager", "The manager can only be set before the component is registered.");
                    return;
                }
                _manager = value;
            }
        }

        public Body Body { get { return _body; } }

        public ObjectType ObjectMask { get { return _collidesWithTypes; } }

        public Box WorldExtents
        {
            get
            {
                Vector2 halfSize = _size * 0.5f;

                Vector2 min = new Vector2(Position.X - halfSize.X, Position.Y - halfSize.Y);
                Vector2 max = new Vector2(Position.X + halfSize.X, Position.Y + halfSize.Y);

                return new Box(min, max);
            }
        }

        public bool CastRay(Vector2 start, Vector2 end, ObjectType mask, ref RayHitInfo result)
        {
            throw new NotImplementedException();
        }

        public bool PointOccupied(Vector2 position, ObjectType mask, IScene2D scene)
        {
            ContainmentType contains = WorldExtents.Contains(position);
            return contains == ContainmentType.Contains || contains == ContainmentType.Intersects;
        }

        public ObjectType CollisionType
        {
            get { return _collisionType; }
            set
            {
                _collisionType = value;
                if (_body != null)
                {
                    BuildCollisionShapes();
                }
            }
        }

        public ObjectType CollidesWithTypes
        {
            get { return _collidesWithTypes; }
            set
            {
                _collidesWithTypes = value;
                if (_body != null)
                {
                    BuildCollisionShapes();
                }
            }
        }

        public Vector2 Position
        {
            get
            {
                if (_body != null)
                {
                    Vector2 pos = _body.GetPosition();
                    return new Vector2(pos.X * _manager.Scale, pos.Y * _manager.Scale);
                }
                return _bodyDef.position;
            }
            set
            {
                _bodyDef.position = value;

                if (_body != null)
                {
                    value *= _manager.InverseScale;
                    _body.SetTransform(value, _body.GetAngle());
                }
            }
        }

        public float Rotation
        {
            get
            {
                float rotation = _bodyDef.angle;
                if (_body != null)
                {
                    rotation = _body.GetAngle();
                }

                return MathHelper.ToDegrees(rotation);
            }
            set
            {
                float rotation = MathHelper.ToRadians(value);
                _bodyDef.angle = rotation;
                if (_body != null)
                {
                    _body.SetTransform(_body.GetPosition(), rotation);
                }
            }
        }

        public Vector2 Size
        {
            get { return _size; }
            set
            {
                _size = value;
                if (_body != null)
                {
                    BuildCollisionShapes();
                }
            }
        }

        public Vector2 LinearVelocity
        {
            get
            {
                if (_body != null)
                {
                    _linearVelocity = _body.GetLinearVelocity() * _manager.Scale;
                }
                return _linearVelocity;
            }
            set
            {
                _linearVelocity = value;

                if (_body != null)
                {
                    Vector2 velocity = _linearVelocity * _manager.InverseScale;
                    _body.SetLinearVelocity(velocity);
                }
            }
        }

        public float AngularVelocity
        {
            get
            {
                if (_body != null)
                {
                    _angularVelocity = MathHelper.ToDegrees(_body.GetAngularVelocity());
                }

                return _angularVelocity;
            }
            set
            {
                _angularVelocity = value;

                if (_body != null)
                {
                    _body.SetAngularVelocity(MathHelper.ToRadians(value));
                }
            }
        }

        public bool CanMove
        {
            get { return _canMove; }
            set
            {
                _canMove = value;

                //_bodyDef.type = _canMove ? BodyType.Dynamic : BodyType.Static;
                if (_body != null)
                {
                    UpdateMass();
                    //_body.SetType(_canMove ? BodyType.Dynamic : BodyType.Static);
                }
            }
        }

        public bool CanRotate
        {
            get { return _canRotate; }
            set
            {
                _canRotate = value;
                if (_body != null)
                {
                    UpdateMass();
                }
            }
        }

        public bool CanSleep
        {
            get { return _canSleep; }
            set
            {
                _canSleep = value;
                _bodyDef.allowSleep = value;
                if (_body != null)
                {
                    _body.AllowSleeping(value);
                }
            }
        }

        public bool CollidesContinuously
        {
            get
            {
                if (_body != null)
                {
                    return _body.IsBullet;
                }
                return _bodyDef.bullet;
            }
            set
            {
                _bodyDef.bullet = value;
                if (_body != null)
                {
                    _body.SetBullet(value);
                }
            }
        }

        public BodyType BodyType
        {
            get
            {
                if (_body != null)
                {
                    return _body.GetType();
                }
                return _bodyDef.type;
            }
            set
            {
                _bodyDef.type = value;
                if (_body != null)
                {
                    _body.SetType(value);
                }
            }
        }

        public CollisionShape[] CollisionShapes
        {
            get { return _collisionShapes; }
            set
            {
                _collisionShapes = value;
                if (_body != null)
                {
                    BuildCollisionShapes();
                }
            }
        }

        public void BuildCollisionShapes()
        {
            if (_body == null)
            {
                Logger.Warn(this, "BuildCollisionShapes", "Cannot build collision shapes prior to registration.");
                return;
            }

            Fixture fixture = _body.GetFixtureList();
            while (fixture != null)
            {
                Fixture nextFixture = fixture.GetNext();
                _body.DestroyFixture(fixture);
                fixture = nextFixture;
            }

            if (_collisionShapes != null)
            {
                foreach (CollisionShape shape in _collisionShapes)
                {
                    _body.CreateFixture(shape.CreateFixture(this));
                }
            }

            UpdateMass();
        }

        public void UpdateMass()
        {
            _body.ResetMassData();
            if (!_canMove || !_canRotate)
            {
                MassData mass = new MassData();
                mass.center = _body.GetLocalCenter();
                if (_canMove)
                {
                    mass.mass = _body.GetMass();
                }
                else
                {
                    mass.mass = 0;
                }

                if (_canRotate)
                {
                    mass.I = _body.GetInertia();
                }
                else
                {
                    mass.I = 0;
                }
                _body.SetMassData(ref mass);
            }
        }

        public override void OnAdd()
        {
            if (_manager == null)
            {
                Logger.Warn(this, "OnAdd", "A Box2DSpatialComponent cannot be registered without a manager."); 
                return; 
            }

            _bodyDef.position *= _manager.InverseScale;
            _bodyDef.userData = this;

            _manager.Add(_bodyDef, (Body body) =>
            {
                _body = body;
                _bodyDef.position *= _manager.Scale;

                BuildCollisionShapes();

                if (OnAddedCallback != null)
                {
                    OnAddedCallback(this);
                }
            });
        }

        public override void OnRemove()
        {
            _manager.Remove(_body);
            _body = null;
        }
    }
}
